﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Green;

namespace Noris.Schedule.Support.Expressions
{
    #region class Variable : Jedna proměnná rozdělená do jednotlivých položek
    /// <summary>
    /// Variable : Jedna proměnná rozdělená do jednotlivých položek
	/// </summary>
	public class Variable
	{
		#region Konstrukce
		public Variable(string text)
		{
			this.VariableName = text;
			this.Items = new List<VariableItem>();
			this.CreateItems(text);
		}
		private void CreateItems(string text)
		{
			string[] parts = text.Split(':');
			foreach (string part in parts)
			{
				VariableItem item = VariableItem.CreateFromItem(this, part);
				if (item != null)
					this.Items.Add(item);
			}
		}
		public override string ToString()
		{
			return this.VariableName;
		}
		#endregion
		#region Property
		/// <summary>
		/// Vstupující text, který je v tomto objektu zachycen a zpracován
		/// </summary>
		public string VariableName { get; private set; }
		/// <summary>
		/// Jednotlivé položky zadané proměnné
		/// </summary>
		public List<VariableItem> Items { get; private set; }
        /// <summary>
        /// Výsledná textová hodnota proměnné.
		/// Nevkládá se explicitně (nelze dát Variable.ResultValue = "hodnota"), k tomu slouží metoda FillResultValue().
        /// </summary>
        public string ResultValue { get; private set; }
		#endregion
		#region Ukládání ResultValue, řešení editačních stylů
		/// <summary>
		/// Uloží do Variable textovou hodnotu nalezeného atributu.
		/// Zde se řeší mj. editační styly.
		/// </summary>
		/// <param name="value"></param>
		public void FillResultValue(object value)
		{
			// Pokud je na vstupu null, uložíme empty string a basta:
			if (value == null)
			{
				this.ResultValue = "";
				return;
			}

			// Podíváme se, zda pro finální atribut lze najít editační styl:
			string text = this.EvaluateEditStyleValue(value);

			// Implicitní konverze value => string:
			if (text == null)
				text = Evaluator.GetString(value);

			this.ResultValue = text;
		}
		/// <summary>
		/// Najde a vrátí vizuální hodnotu pro danou datovou hodnotu.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		protected string EvaluateEditStyleValue(object value)
		{
            if (value == null) return "";

            Type valueType = value.GetType();
            if (valueType.IsEnum)
    			// 1. Pokud value je enum:
                return EvaluateEditStyleEnum(value);
            else
    			// 2. Standardní Green edit style:
                return EvaluateEditStyleGreen(value);
        }
        /// <summary>
        /// Najde a vrátí vizuální hodnotu pro datovou hodnotu podle editačního stylu atributu Helios Green
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string EvaluateEditStyleEnum(object value)
        {
            Type valueType = value.GetType();
            if (!valueType.IsEnum || !valueType.IsEnumDefined(value)) return "";

            return Repository.EditStyles.GetDispVal(value);
        }
        /// <summary>
        /// Najde a vrátí vizuální hodnotu pro datovou hodnotu podle editačního stylu atributu Helios Green
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string EvaluateEditStyleGreen(object value)
        {
            // Pro vyhodnocení editačního stylu potřebuji najít odpovídající Green atribut, tedy potřebuji určit třídu a v ní atribut najít:
			int cnt = this.Items.Count;
			if (cnt < 2) return null;

			VariableItem prevItem = this.Items[cnt - 2];      // Entita před atributem (může to být Class, anebo Relation)
			if (!prevItem.IsStandardClass) return null;

			VariableItem attrItem = this.Items[cnt - 1];      // Atribut
			bool isEntry = attrItem.IsEntryTable;
			NorisAttributesCls attribute = prevItem.ClassInfo.FindAttribute(attrItem.AttributeName, attrItem.TableType);
			if (attribute == null) return null;

			if (!attribute.HasEditStyle) return null;         // Editační styl
			EditStyleInfo editStyle = attribute.EditStyle;
            if (editStyle == null) return null;

			return editStyle.GetDispVal(value);
		}
		#endregion
	}
	#endregion
    #region class VariableItem : Jedna položka proměnné (třída, vztah, atribut)
    /// <summary>
    /// ExpressionItem : Jedna položka proměnné (třída, vztah, atribut)
	/// </summary>
	public class VariableItem
	{
		#region Konstrukce
		private VariableItem(Variable parent)
		{
			this.Parent = parent;
		}
		public static VariableItem CreateFromItem(Variable parent, string text)
		{
			if (String.IsNullOrEmpty(text)) return null;
			string tt = text.Trim();
			if (tt.Length <= 1) return null;
			if (tt.Contains('.'))
			{	// "Master.qty_plan" :
				int dot = tt.IndexOf('.');
				if (dot > 0 && dot < (tt.Length - 1))
				{
					VariableItem item = new VariableItem(parent);
					item.ItemType = VariableItemType.Attribute;
					item.AttributeTable = tt.Substring(0, dot);
					item.AttributeName = tt.Substring(dot + 1);
					return item;
				}
			}
			else
			{	// "C1180" / "R101453" / "F66000" :
				char type = tt[0];
                if (type == 'C' || type == 'R' || type == 'L' || type == 'E' || type == 'F')
				{
					int number;
					if (Int32.TryParse(tt.Substring(1), out number))
					{
						VariableItem item = new VariableItem(parent);
						switch (type)
						{
							case 'C':
								item.ItemType = VariableItemType.Class;
								item.ClassNumber = number;
								return item;
							case 'R':
								item.ItemType = VariableItemType.RelationToRight;
								item.RelationNumber = number;
								return item;
							case 'L':
								item.ItemType = VariableItemType.RelationToLeft;
								item.RelationNumber = number;
								return item;
                            case 'E':
                                item.ItemType = VariableItemType.RelationExtended;
                                item.RelationNumber = number;
                                return item;
                            case 'F':
                                item.ItemType = VariableItemType.RelationFolder;
                                item.RelationNumber = number;
                                return item;
                        }
					}
				}
			}
			return null;
		}
		public override string ToString()
		{
			string result = this.ItemType.ToString();
			switch (this.ItemType)
			{
				case VariableItemType.Class:
					result += ": " + this.ClassNumber.ToString();
					break;
				case VariableItemType.RelationToRight:
				case VariableItemType.RelationToLeft:
					result += ": " + this.RelationNumber.ToString();
					break;
                case VariableItemType.RelationExtended:
                    result += ": " + this.RelationNumber.ToString() + " (Extended)";
                    break;
                case VariableItemType.RelationFolder:
                    result += ": " + this.RelationNumber.ToString() + " (Folder)";
                    break;
                case VariableItemType.Attribute:
					result += ": " + this.AttributeTable + "." + this.AttributeName;
					break;
			}
			return result;
		}
		#endregion
		#region Property základní
		/// <summary>
		/// Parent = celý výraz
		/// </summary>
		public Variable Parent { get; private set; }
		/// <summary>
		/// Typ údaje v tomto prvku (třída / vztah doprava / vztah doleva / atribut)
		/// </summary>
		public VariableItemType ItemType { get; private set; }
		/// <summary>
		/// Číslo třídy
		/// </summary>
		public int ClassNumber { get; private set; }
		/// <summary>
		/// Číslo vztahu
		/// </summary>
		public int RelationNumber { get; private set; }
		/// <summary>
		/// Název tabulky
		/// </summary>
		public string AttributeTable { get; private set; }
		/// <summary>
		/// Název atributu
		/// </summary>
		public string AttributeName { get; private set; }
        /// <summary>
        /// Příznak, že hledání této položky v paměti bylo úspěšné.
        /// </summary>
        public bool InMemoryFound { get; set; }
        /// <summary>
        /// Číslo záznamu (Master) nalezené v paměti, slouží jako případná výchozí hodnota pro hledání v SQL serveru.
		/// Odpovídá RecordNumber = cislo_subjektu / cislo_nonsubjektu.
        /// Varianta 1 pro záznamy typu ItemType == Class: vztahuje se ke zdejšímu číslu třídy (this.ClassNumber).
        /// Varianta 2 pro záznamy typu ItemType == Relation: obsahuje číslo vztaženého záznamu příští úrovně (nalezeno v záznamu minulé úrovně).
        /// </summary>
        public Int32? InMemoryRecordNumberMaster { get; set; }
		/// <summary>
		/// Číslo položky záznamu (Entry) nalezené v paměti, slouží jako případná výchozí hodnota pro hledání v SQL serveru.
		/// Odpovídá ItemNumber = cislo_objektu.
		/// Varianta 1 pro záznamy typu ItemType == Class: vztahuje se ke zdejšímu číslu třídy (this.ClassNumber).
		/// Varianta 2 pro záznamy typu ItemType == Relation: obsahuje číslo vztaženého záznamu příští úrovně (nalezeno v záznamu minulé úrovně).
		/// </summary>
		public Int32? InMemoryRecordNumberEntry { get; set; }
		/// <summary>
		/// Nalezený datový objekt v paměti (pro tuto úroveň)
		/// </summary>
		public object InMemoryDataObject { get; set; }
		/// <summary>
		/// Nalezená hodnota atributu v paměti (pro tuto úroveň = tj. pro poslední úroveň)
		/// </summary>
		public object InMemoryDataValue { get; set; }
		#endregion
		#region Property odvozené, internal
		/// <summary>
		/// true pokud je zde (podle ItemType) Class
		/// </summary>
		internal bool IsClass { get { return (this.ItemType == VariableItemType.Class); } }
		/// <summary>
		/// true pokud je zde standardní Green Class (tj. je IsClass, a this.ClassInfo není null)
		/// </summary>
		internal bool IsStandardClass { get { return (this.IsClass && this.ClassInfo != null); } }
		/// <summary>
		/// true pokud je zde standardní subjektová Green Class (tj. je IsStandardClass a this.ClassInfo je IsSubject)
		/// </summary>
		internal bool IsStandardSubjectClass { get { return (this.IsStandardClass && this.ClassInfo.IsSubject); } }
		/// <summary>
		/// true pokud je zde standardní Green položková třída
		/// </summary>
		internal bool IsStandardEntriesClass { get { return (this.IsStandardClass && this.ClassInfo.HasEntries); } }
		/// <summary>
		/// true pokud je zde (podle ItemType) Attribute
		/// </summary>
		internal bool IsAttribute { get { return (this.ItemType == VariableItemType.Attribute); } }
		/// <summary>
		/// true pokud je zde (podle ItemType: Left/Right/Extended) Relation
		/// </summary>
        internal bool IsRelation { get { return (this.ItemType == VariableItemType.RelationToLeft || this.ItemType == VariableItemType.RelationToRight || this.ItemType == VariableItemType.RelationExtended || this.ItemType == VariableItemType.RelationFolder); } }
		/// <summary>
        /// true pokud je zde standardní Green Relation (tj. je IsRelation, není IsExtendedRelation a this.RelationInfo není null)
		/// </summary>
		internal bool IsStandardRelation { get { return (this.IsRelation && this.ItemType != VariableItemType.RelationExtended && this.RelationInfo != null); } }
        /// <summary>
        /// true pokud je zde (podle ItemType: RelationExtended) rozšířený vztah, definovaný ClassTree extenderem
        /// </summary>
        internal bool IsExtendedRelation { get { return (this.ItemType == VariableItemType.RelationExtended); } }
        /// <summary>
        /// true pokud je zde (podle ItemType: RelationFolder) vztah na pořadač (definovaný ClassTree extenderem)
        /// </summary>
        internal bool IsFolderRelation { get { return (this.ItemType == VariableItemType.RelationFolder); } }
        /// <summary>
        /// true pokud je zde hlavičkový vztah (statický nebo dynamický)
        /// </summary>
        internal bool IsMasterRelation
        {
            get
            {
                if (!this.IsStandardRelation) return false;
                return this.RelationInfo.IsMaster;
            }
        }
        /// <summary>
        /// true pokud je zde položkový vztah (statický nebo dynamický)
        /// </summary>
        internal bool IsEntryRelation
        {
            get
            {
                if (!this.IsStandardRelation) return false;
                return this.RelationInfo.IsEntry;
            }
        }
        /// <summary>
		/// Druh vztahu, který zde evidujeme
		/// </summary>
		internal RelationStatDynType RelationStatDyn { get { return (this.IsStandardRelation ? this.RelationInfo.RelationStatDyn: RelationStatDynType.None); } }
        /// <summary>
        /// Strana vztahu, odvozená od this.ItemType
        /// </summary>
        internal RelationSideType RelationSide { get { return (this.ItemType == VariableItemType.RelationToLeft ? RelationSideType.Left : (this.ItemType == VariableItemType.RelationToRight ? RelationSideType.Right : RelationSideType.None)); } }
        /// <summary>
		/// Název master tabulky včetně prefixu "lcs.", anebo null pokud zde není standardní green třída
		/// </summary>
		internal string ClassMasterTable { get { return (this.IsStandardClass ? this.ClassInfo.NazevTabulky : null); } }
		/// <summary>
		/// Název master tabulky včetně prefixu "lcs.", anebo null pokud zde není standardní green třída
		/// </summary>
		internal string ClassEntriesTable { get { return (this.IsStandardEntriesClass ? this.ClassInfo.TabulkaObjects : null); } }
		/// <summary>
		/// Typ tabulky, který potřebujeme mít v selectu pro tuto entitu.
		/// Pokud jsme třída, pak vrací None (touto cestou nejde určit typ tabulky).
		/// Pokud jsme atribut, pak this.TableType se určuje na základě hodnoty v this.AttributeTable.
		/// Pokud jsme vztah, pak odpovídá typu vztahu (statický / dynamický, hlavičkový / položkový).
        /// Pozor: dynamické vztahy nemají určovat typ tabulky, protože používají speciální vztahové tabulky (lcs.vztahysubjektu a lcs.objektsubjekt). Zde tedy vracejí None.
		/// Tato property neřeší stranu vztahu: pro vztahy zleva i zprava obsahuje typ tabulky pro vztah.
		/// </summary>
		internal ClassTableType TableType
		{
			get
			{
				if (this.IsClass) return ClassTableType.None;
				if (this.IsAttribute) return this._GetTableTypeAttribute();
				if (this.IsRelation)
				{
					if (this.RelationStatDyn == RelationStatDynType.Static)
						return this._GetTableTypeRelationStatic();
					else if (this.RelationStatDyn == RelationStatDynType.Dynamic)
						return this._GetTableTypeRelationDynamic();
				}
				return ClassTableType.None;
			}
		}
        /// <summary>
        /// Vrací příznak true, že pro získání potřebných dat z tabulky je třeba brát tabulku hlavičkovou (anebo subjektovou anebo uda master).
        /// Funguje pro atributy a pro standardní statické vztahy.
        /// Využívá se typicky při určování typu JOINů při skládání selectu pro SQL příkaz pro získání dat. Pro Master tabulky se JOINuje / filtruje jen podle čísla Master.
        /// Pro nestandardní (Extendend) vztahy a pro Class vrací false.
        /// </summary>
        internal bool IsMasterTable
        {
            get
            {
                if (this.IsAttribute)
                {
                    ClassTableType tableType = _GetTableTypeAttribute();
                    return (tableType == ClassTableType.Master || tableType == ClassTableType.UdaMaster || tableType == ClassTableType.Subjekt);
                }
                else if (this.IsStandardRelation)
                {
                    RelationTypType relationType = this.RelationInfo.RelationTyp;
                    return (relationType == RelationTypType.SubjectSubject || relationType == RelationTypType.SubjectNonsubject || relationType == RelationTypType.NonsubjectSubject || relationType == RelationTypType.NonsubjectNonsubject);
                }
                return false;
            }
        }
        /// <summary>
        /// Vrací příznak true, že pro získání potřebných dat z tabulky je třeba brát tabulku položek (anebo uda položky).
        /// Funguje pro atributy a pro standardní statické vztahy.
        /// Využívá se typicky při určování typu JOINů při skládání selectu pro SQL příkaz pro získání dat. Pro Entry tabulky se JOINuje / filtruje podle čísla Master + Entry.
        /// Pro nestandardní (Extendend) vztahy a pro Class vrací false.
        /// </summary>
        internal bool IsEntryTable
        {
            get
            {
                if (this.IsAttribute)
                {
                    ClassTableType tableType = _GetTableTypeAttribute();
                    return (tableType == ClassTableType.Entries || tableType == ClassTableType.UdaEntries);
                }
                else if (this.IsStandardRelation)
                {
                    RelationTypType relationType = this.RelationInfo.RelationTyp;
                    return (relationType == RelationTypType.ObjectSubject || relationType == RelationTypType.ObjectNonsubject);
                }
                return false;
            }
        }
		/// <summary>
		/// Vrátí typ tabulky pro atribut
		/// </summary>
		/// <returns></returns>
		private ClassTableType _GetTableTypeAttribute()
		{
			string tableName = this.AttributeTable;
			if (String.IsNullOrEmpty(tableName)) return ClassTableType.None;
			switch (tableName.Trim())
			{
				case TABLENAME_SUBJEKT: return ClassTableType.Subjekt;
				case TABLENAME_MASTER: return ClassTableType.Master;
				case TABLENAME_MASTERUDA: return ClassTableType.UdaMaster;
				case TABLENAME_ENTRIES: return ClassTableType.Entries;
				case TABLENAME_ENTRIESUDA: return ClassTableType.UdaEntries;
				case TABLENAME_EXTENDED: return ClassTableType.None;
				case TABLENAME_SYSTEM: return ClassTableType.Master;
				case TABLENAME_RECORDNUMBER: return ClassTableType.Master;
			}
			return ClassTableType.None;
		}
		/// <summary>
		/// Vrátí typ tabulky pro statický vztah
		/// </summary>
		/// <returns></returns>
		private ClassTableType _GetTableTypeRelationStatic()
		{
			if (!this.IsStandardRelation) return ClassTableType.None;
			// Máme vztah, pro něj známe db sloupec:
			RelationInfo relationInfo = this.RelationInfo;
			ClassInfo classInfo = this.RelationInfo.SourceClassData;  // Data o master třídě vztahu
			switch (this.RelationInfo.RelationTyp)
			{
				case RelationTypType.SubjectSubject:
				case RelationTypType.SubjectNonsubject:
				case RelationTypType.NonsubjectSubject:
				case RelationTypType.NonsubjectNonsubject:
				case RelationTypType.ObjectSubject:
				case RelationTypType.ObjectNonsubject:
					return classInfo.DetectRelationTable(relationInfo);
			}
			return ClassTableType.None;
		}
		/// <summary>
		/// Vrátí typ tabulky pro dynamický vztah.
        /// Dynamické vztahy by tudy chodit neměly, protože si nevybírají tabulku třídy, ale používají tabulky specifické pro dynamické vztahy!
		/// </summary>
		/// <returns></returns>
		private ClassTableType _GetTableTypeRelationDynamic()
		{
			return ClassTableType.None;
		}
		#endregion
        #region Property dotahované z repozitory
        /// <summary>
        /// Obsahuje referenci na data o třídě (this.ClassNumber).
        /// Buďme v klidu při častém používání této property: její obsah se on-demand načte z repozitory do místní proměnné, a následně se výdy používá zdejší reference.
        /// </summary>
        public ClassInfo ClassInfo { get { this._ClassInfoCheckLoad(); return this._ClassInfo; } }
        /// <summary>
        /// Obsahuje referenci na data o vztahu (this.RelationNumber).
        /// Buďme v klidu při častém používání této property: její obsah se on-demand načte z repozitory do místní proměnné, a následně se výdy používá zdejší reference.
        /// </summary>
        public RelationInfo RelationInfo { get { this._RelationInfoCheckLoad(); return this._RelationInfo; } }
        private ClassInfo _ClassInfo;
        private RelationInfo _RelationInfo;
        private bool _ClassInfoLoaded;
        private bool _RelationInfoLoaded;
        private void _ClassInfoCheckLoad()
        {
            if (this._ClassInfoLoaded) return;
            ClassInfo classInfo;
            if (this.ClassNumber != 0 && Repository.Classes.TryGetValue(this.ClassNumber, out classInfo))
                this._ClassInfo = classInfo;
            this._ClassInfoLoaded = true;
        }
        private void _RelationInfoCheckLoad()
        {
            if (this._RelationInfoLoaded) return;
            RelationInfo relationInfo;
            if (this.RelationNumber != 0 && Repository.Relations.TryGetValue(this.RelationNumber, out relationInfo))
                this._RelationInfo = relationInfo;
            this._RelationInfoLoaded = true;
        }
        #endregion
		#region Konstanty (používané i z UI) - definují symbolická jména tabulek
		public const string TABLENAME_SUBJEKT = ClassTree.TREEPATH_TABLE_SUBJECT;
		public const string TABLENAME_MASTER = ClassTree.TREEPATH_TABLE_MASTER;
		public const string TABLENAME_MASTERUDA = ClassTree.TREEPATH_TABLE_MASTER_UDA;
		public const string TABLENAME_ENTRIES = ClassTree.TREEPATH_TABLE_ENTRIES;
		public const string TABLENAME_ENTRIESUDA = ClassTree.TREEPATH_TABLE_ENTRIES_UDA;
		public const string TABLENAME_EXTENDED = "Extended";
		public const string TABLENAME_SYSTEM = "System";
		public const string TABLENAME_RECORDNUMBER = "RecordNumber";
		#endregion
	}
	#endregion
    #region enum VariableItemType : druh jedné položky proměnné (jedna položka například = "C1188", Druh je odvozen od písmene C = Class)
    /// <summary>
    /// VariableItemType : druh jedné položky proměnné (jedna položka například = "C1188", Druh je odvozen od písmene C = Class)
	/// </summary>
	public enum VariableItemType
	{
		None = 0,
        /// <summary>
        /// Zde je uložena informace o datech konkrétní třídy
        /// </summary>
		Class = 'C',
        /// <summary>
        /// Zde je uložena informace o vztahu na další data.
        /// Vztah ukazuje doprava na další třídu, například z položky dokladu (předchozí položka VariableItem) na záznam do číselníku (následující položka).
        /// Anebo dynamický vztah: předchozí položka VariableItem je vlevo, a napravo ve vztahu bude následující položka.
        /// </summary>
        RelationToRight = 'R',
        /// <summary>
        /// Zde je uložena informace o vztahu na další data.
        /// Vztah ukazuje doleva, například ze záznamu číselníku (předchozí položka VariableItem), na všechny položky dokladů (následující položka), které ukazují na tento číselník.
        /// Anebo dynamický vztah: předchozí položka VariableItem je vpravo, a nalevo ve vztahu bude následující položka.
        /// </summary>
        RelationToLeft = 'L',
        /// <summary>
        /// Zde je uložena informace o vztahu na další data.
        /// Vztah je Extended, nejde o standardní vztah Greenu. Vztah definoval objekt IClassTreeExtender.
        /// </summary>
        RelationExtended = 'E',
        /// <summary>
        /// Zde je uložena informace o vztahu na pořadač.
        /// Vztah je na pomezí mezi Standard a Extended, nejde o standardní vztah Greenu (nemá svůj záznam v repozitory).
        /// Ale přesto jej Evaluator může vyhodnotit, protože obecně dokáže najít číslo pořadače v záznamu Subjekt nebo master jako column "cislo_poradace", 
        /// a cílovou třídou je třída 9: Pořadač.
        /// Vztah definoval objekt IClassTreeExtender.
        /// </summary>
        RelationFolder = 'F',
        /// <summary>
        /// Zde je uložena informace o atributu, jehož hodnota se bude zobrazovat.
        /// </summary>
        Attribute = 'A'
	}
	#endregion
}
